#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>

union val {
	int i_val;
	struct linger linger_val;
	struct timeval timeval_val;
} val;

static char* sock_str_flag(union val*, int);
static char* sock_str_int(union val*, int);
static char* sock_str_linger(union val*, int);
static char* sock_str_timeval(union val*, int);

struct sock_opt {
	const char *opt_str;
	int opt_level;
	int opt_name;
	char* (*opt_val_str)(union val*, int);
} sock_opts[] = {
	{"SO_BROADCAST", SOL_SOCKET, SO_BROADCAST, sock_str_flag},
	{"SO_DEBUG", SOL_SOCKET, SO_DEBUG, sock_str_flag},
	{"SO_DONTROUTE", SOL_SOCKET, SO_DONTROUTE, sock_str_flag}, 
	{"SO_ERROR", SOL_SOCKET, SO_ERROR, sock_str_int},
	{"SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, sock_str_flag},
	{"SO_LINGER", SOL_SOCKET, SO_LINGER, sock_str_linger},
	{"SO_OOBINLINE", SOL_SOCKET, SO_OOBINLINE, sock_str_flag},
	{"SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, sock_str_int},
	{"SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, sock_str_int},
	{"SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, sock_str_int},
	{"SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, sock_str_int},
	{"SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, sock_str_timeval},
	{"SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, sock_str_timeval},
	{"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, sock_str_flag},
	{"SO_REUSEPORT", SOL_SOCKET, SO_REUSEPORT, sock_str_flag},
	{"SO_TYPE", SOL_SOCKET, SO_TYPE, sock_str_int},
#ifdef SO_USELOOPBACK
	{"SO_USELOOPBACK", SOL_USELOOPBACK, SO_USELOOPBACK, sock_str_flag},
#endif
	{"IP_TOS", IPPROTO_IP, IP_TOS, sock_str_int},
	{"IP_TTL", IPPROTO_IP, IP_TTL, sock_str_int},
#ifdef	IPV6_V6ONLY
	{ "IPV6_V6ONLY", IPPROTO_IPV6,IPV6_V6ONLY,sock_str_flag },
#else
	{ "IPV6_V6ONLY", 0,0, NULL },
#endif
	{NULL, 0, 0, NULL}
};

int main() {
	int fd;
	for(struct sock_opt* opt = sock_opts; opt->opt_str != NULL; ++opt) {
		switch(opt->opt_level) {
			case SOL_SOCKET:
			case IPPROTO_IP: {
				fd = socket(AF_INET, SOCK_STREAM, 0);
				break;
			 }
			case IPPROTO_IPV6: {
				fd = socket(AF_INET6, SOCK_STREAM, 0);
				break;
			}
			default:{
				fd = -1;
				break;
			}
		}
		if(fd == -1) continue;
		printf("%s:", opt->opt_str);
		socklen_t len = sizeof(val);
		if(getsockopt(fd, opt->opt_level, opt->opt_name, &val, &len) != 0) {
			printf("get sockopt error. level=%d opt_name=%s. errno=%d errmsg=%s\n", 
					opt->opt_level, opt->opt_str, errno, strerror(errno));
		} else
		    printf("%s\n",opt->opt_val_str(&val, len));
	}
	return 0;
}

char buf[1024];
char* sock_str_flag(union val* val, int len) {
	int pval = val->i_val;
	if(len != sizeof(int)) 
		snprintf(buf, 1024, "len=%d not sizeof(int)", len);
	else
		snprintf(buf, 1024, pval == 0 ? "off" : "on");
	return buf;
}

char* sock_str_int(union val* val, int len) {
	int pval = val->i_val;
	if(len != sizeof(int))
		snprintf(buf, 1024, "len=%d not sizeof(int)", len);
	else
		snprintf(buf, 1024, "%d", pval);
	return buf;
}

char* sock_str_linger(union val* val, int len) {
	struct linger* pval = &val->linger_val;
	if(len != sizeof(struct linger))
		snprintf(buf, 1024, "len=%d not sizeof(struct linger)", len);
	else
		snprintf(buf, 1024, "l_onoff=%d l_linger=%d", pval->l_onoff, pval->l_linger);
	return buf;
}

char* sock_str_timeval(union val* val, int len) {
	struct timeval *pval = &val->timeval_val;
	if(len != sizeof(struct timeval))
		snprintf(buf, 1024, "len=%d not sizeof(struct timeval)", len);
	else
		snprintf(buf, 1024, "tv_sec=%ld tv_usec=%ld", pval->tv_sec, pval->tv_usec);

	return buf;
}
